package io.gitee.zhangbinhub.acp.core.common.security

import io.gitee.zhangbinhub.acp.core.common.base.BaseEncrypt
import io.gitee.zhangbinhub.acp.core.common.security.key.KeyManagement
import org.bouncycastle.asn1.gm.GMNamedCurves
import org.bouncycastle.crypto.engines.SM2Engine
import org.bouncycastle.crypto.params.*
import org.bouncycastle.crypto.signers.SM2Signer
import java.math.BigInteger
import java.security.SecureRandom


object SM2Encrypt : BaseEncrypt() {
    const val PRIVATE_KEY_RADIX = 16
    const val SM2_STD = "sm2p256v1"
    private const val SM2_PID = "1234567812345678"

    @JvmStatic
    @Throws(Exception::class)
    @JvmOverloads
    fun encrypt(
        plainText: String,
        publicKey: String,
        modeType: Int = 1,
        inputEncode: ByteEncodeEnum = ByteEncodeEnum.BYTE,
        outputEncode: ByteEncodeEnum = ByteEncodeEnum.BASE64,
        publicKeyEncode: ByteEncodeEnum = ByteEncodeEnum.HEX
    ): String {
        val ecKeyParameters = getECKeyParameters(publicKey, false, publicKeyEncode = publicKeyEncode)
        val sm2Engine = SM2Engine(
            when (modeType) {
                1 -> SM2Engine.Mode.C1C3C2
                else -> SM2Engine.Mode.C1C2C3
            }
        )
        val secureRandom = SecureRandom()
        secureRandom.setSeed(
            decodeToBytes(
                KeyManagement.getRandomString(KeyManagement.RANDOM_STR, 32), ByteEncodeEnum.BYTE
            )
        )
        sm2Engine.init(true, ParametersWithRandom(ecKeyParameters, secureRandom))
        val cipherDataByte = decodeToBytes(plainText, inputEncode)
        return encodeToString(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.size), outputEncode)
    }

    @JvmStatic
    @Throws(Exception::class)
    @JvmOverloads
    fun decrypt(
        encryptedText: String,
        privateKey: String,
        modeType: Int = 1,
        inputEncode: ByteEncodeEnum = ByteEncodeEnum.BASE64,
        outputEncode: ByteEncodeEnum = ByteEncodeEnum.BYTE,
        privateKeyRadix: Int = PRIVATE_KEY_RADIX
    ): String {
        val ecKeyParameters = getECKeyParameters(privateKey, true, privateKeyRadix = privateKeyRadix)
        val sm2Engine = SM2Engine(
            when (modeType) {
                1 -> SM2Engine.Mode.C1C3C2
                else -> SM2Engine.Mode.C1C2C3
            }
        )
        sm2Engine.init(false, ecKeyParameters)
        val cipherDataByte = decodeToBytes(encryptedText, inputEncode)
        return encodeToString(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.size), outputEncode)
    }

    @JvmStatic
    @Throws(Exception::class)
    @JvmOverloads
    fun sign(
        data: String,
        privateKey: String,
        dataEncode: ByteEncodeEnum = ByteEncodeEnum.BYTE,
        signEncode: ByteEncodeEnum = ByteEncodeEnum.BASE64,
        privateKeyRadix: Int = PRIVATE_KEY_RADIX,
        id: String = SM2_PID,
        idEncode: ByteEncodeEnum = ByteEncodeEnum.BYTE
    ): String {
        val ecKeyParameters = getECKeyParameters(privateKey, true, privateKeyRadix = privateKeyRadix)
        val sm2Signer = SM2Signer()
        val secureRandom = SecureRandom()
        secureRandom.setSeed(
            decodeToBytes(
                KeyManagement.getRandomString(KeyManagement.RANDOM_STR, 32), ByteEncodeEnum.BYTE
            )
        )
        sm2Signer.init(
            true, ParametersWithID(
                ParametersWithRandom(ecKeyParameters, secureRandom), decodeToBytes(id, idEncode)
            )
        )
        val message = decodeToBytes(data, dataEncode)
        sm2Signer.update(message, 0, message.size)
        return encodeToString(sm2Signer.generateSignature(), signEncode)
    }

    @JvmStatic
    @Throws(Exception::class)
    @JvmOverloads
    fun verify(
        data: String,
        publicKey: String,
        sign: String,
        dataEncode: ByteEncodeEnum = ByteEncodeEnum.BYTE,
        signEncode: ByteEncodeEnum = ByteEncodeEnum.BASE64,
        publicKeyEncode: ByteEncodeEnum = ByteEncodeEnum.HEX,
        id: String = SM2_PID,
        idEncode: ByteEncodeEnum = ByteEncodeEnum.BYTE
    ): Boolean {
        val ecKeyParameters = getECKeyParameters(publicKey, false, publicKeyEncode = publicKeyEncode)
        val sm2Signer = SM2Signer()
        sm2Signer.init(
            false, ParametersWithID(
                ecKeyParameters, decodeToBytes(id, idEncode)
            )
        )
        val message = decodeToBytes(data, dataEncode)
        sm2Signer.update(message, 0, message.size)
        return sm2Signer.verifySignature(decodeToBytes(sign, signEncode))
    }

    @Throws(Exception::class)
    private fun getECKeyParameters(
        key: String,
        private: Boolean,
        publicKeyEncode: ByteEncodeEnum = ByteEncodeEnum.HEX,
        privateKeyRadix: Int = PRIVATE_KEY_RADIX
    ): ECKeyParameters {
        val sm2ECParameters = GMNamedCurves.getByName(SM2_STD)
        val domainParameters = ECDomainParameters(
            sm2ECParameters.curve, sm2ECParameters.g, sm2ECParameters.n
        )
        return if (private) {
            ECPrivateKeyParameters(BigInteger(key, privateKeyRadix), domainParameters)
        } else {
            ECPublicKeyParameters(
                sm2ECParameters.curve.decodePoint(decodeToBytes(key, publicKeyEncode)), domainParameters
            )
        }
    }
}